using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace Shrapnel
{
	public struct BoundingBox
	{
		private PointF location;
		private SizeF size;

		/// <summary>
		/// Location of upper-left corner of the bounding box
		/// </summary>
		public PointF Location
		{
			get { return location; }
			set { location = value; }
		}

		/// <summary>
		/// Size of this boundingbox
		/// </summary>
		public SizeF Size 
		{
			get { return size; }
			set { size = value; }
		}

		/// <summary>
		/// Create a new bounding box out of a rectangle
		/// </summary>
		/// <param name="rect"></param>
		public BoundingBox(RectangleF rect)
		{
			location = rect.Location;
			size = rect.Size;
		}

		/// <summary>
		/// Create a new bounding box from a position and a size. 
		/// </summary>
		/// <param name="topLeft"></param>
		/// <param name="size"></param>
		public BoundingBox(PointF topLeft, SizeF size)
		{
			this.location = topLeft;
			this.size = size;
		}


		/// <summary>
		/// Modify this bounding box to also include the provided box
		/// </summary>
		/// <param name="other">Other box to include</param>
		public void Union(BoundingBox other)
		{
			PointF bottomRight = new PointF(location.X + size.Width, location.Y + size.Height);
			PointF otherBottomRight = new PointF(other.location.X + other.size.Width, other.location.Y + other.size.Height);

			location.X = Math.Min(location.X, other.Location.X);
			location.Y = Math.Min(location.Y, other.Location.Y);
			bottomRight.X = Math.Max(bottomRight.X, otherBottomRight.X);
			bottomRight.Y = Math.Max(bottomRight.Y, otherBottomRight.Y);

			size.Width = bottomRight.X - location.X;
			size.Height = bottomRight.Y - location.Y;
		}



		/// <summary>
		/// Determine if we overlap with the other (or if we're inside the other, 
		/// or vice-versa).
		/// </summary>
		/// <param name="other">The other bounding box</param>
		/// <returns>True, when we overlap with the other box</returns>
		public bool OverlapsWith(BoundingBox other)
		{
			// Separating axis method

			if ((location.X >= other.Location.X + other.Size.Width) ||
				(location.Y >= other.location.Y + other.Size.Height) ||
				(location.X+size.Width <= other.Location.X) ||
				(location.Y+size.Height <= other.location.Y))
			{
				return false; // No overlap whatsoever
			} else 
			{
				return true;
			}
		}


		/// <summary>
		/// Determine if we overlap with a (infinitely thin) line from start to end
		/// </summary>
		/// <param name="start"></param>
		/// <param name="end"></param>
		/// <returns>True when the line intersects our box, or True when the line is completely inside our box</returns>
		public bool OverlapsWith(PointF start, PointF end)
		{
			bool startIn = IsInside(start);
			bool endIn = IsInside(end);

			if (startIn || endIn)
				return true; // when any of our two points is inside, we overlap.
			
			// okay both start and end are not inside our box, but we might intersect with any of our box' edges

			PointF topLeft = location;
			PointF topRight = new PointF(topLeft.X + size.Width, topLeft.Y);
			PointF bottomLeft = new PointF(topLeft.X, topLeft.Y + size.Height);
			PointF bottomRight = new PointF(topLeft.X + size.Width, topLeft.Y + size.Height);

			// check each line for intersection
			if (LineIntersectLine(start, end, topLeft, bottomLeft)) return true;
			if (LineIntersectLine(start, end, bottomLeft, bottomRight)) return true;
			if (LineIntersectLine(start, end, topLeft, topRight)) return true;
			if (LineIntersectLine(start, end, topRight, bottomRight)) return true;

			return false;
		}


		/// <summary>
		/// Determine if a line (v1,v2) intersects a line (v3,v4). This doesn't gracefully handle parallel lines though.
		/// 
		/// Algorithm thanks to "Newera", from:
		/// http://www.gamedev.net/community/forums/topic.asp?topic_id=440350
		/// </summary>
		/// <param name="v1"></param>
		/// <param name="v2"></param>
		/// <param name="v3"></param>
		/// <param name="v4"></param>
		/// <returns></returns>
		private bool LineIntersectLine(PointF v1, PointF v2, PointF v3, PointF v4)
		{
			float denom = ((v4.Y - v3.Y) * (v2.X - v1.X)) - ((v4.X - v3.X) * (v2.Y - v1.Y));
			float numerator = ((v4.X - v3.X) * (v1.Y - v3.Y)) - ((v4.Y - v3.Y) * (v1.X - v3.X));
			float numerator2 = ((v2.X - v1.X) * (v1.Y - v3.Y)) - ((v2.Y - v1.Y) * (v1.X - v3.X));

			if ( denom == 0.0f )
			{
				if ( numerator == 0.0f && numerator2 == 0.0f )
				{
					return false; //COINCIDENT;
				}
				return false;// PARALLEL;
			}
			float ua = numerator / denom;
			float ub = numerator2/ denom;

			return (ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f);
		}





		/// <summary>
		/// See if a given point is encompassed in this box
		/// </summary>
		/// <param name="point"></param>
		/// <returns></returns>
		public bool IsInside(PointF point)
		{
			return (
				point.X >= location.X &&
				point.Y >= location.Y &&
				point.X < location.X + size.Width &&
				point.Y < location.Y + size.Height);
		}
	}
}
